﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Windows.Media.Animation;
using Kinect.Framework.Interfaces;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using System.Media;
using ManagedNite;
using Kinect.Framework.Lib;

namespace Kinect.Framework.Controls
{
    [TemplatePart(Name = KinectListBox.ElementContentListbox, Type = typeof(ListBox))]
    [TemplatePart(Name = KinectListBox.ElementContentListbox, Type = typeof(KinectListBoxScrollViewer))]
    [TemplatePart(Name = KinectListBox.ElementContentSelectedZone, Type = typeof(Rectangle))]
    public class KinectListBox : ListBox, IKinectElement
    {
        #region Constants
        private const string ElementContentListbox = "PART_ContentListbox";
        private const string ElementContentAnimatedScrollViewer = "PART_ContentAnimatedScrollViewer";
        private const string ElementContentSelectedZone = "PART_ContentSelectedZone";
        #endregion

        #region Data
        private ListBox contentListbox;
        private KinectListBoxScrollViewer contentAnimatedScrollViewer;
        private Rectangle contentSelectedZone;
        //private SoundEffect soundEffect;

        #endregion

        #region Events GotKinectFocused
        public delegate void GotKinectFocusedEventHandler(object s, PayloadEventArgs<IKinectElement> e);
        public event GotKinectFocusedEventHandler GotKinectFocused;
        public void OnGotKinectFocused(IKinectElement value)
        {
            if (GotKinectFocused != null)
                GotKinectFocused(this, new PayloadEventArgs<IKinectElement>(value));
        }
        #endregion

        #region Events LostKinectFocused
        public delegate void LostKinectFocusedEventHandler(object s, PayloadEventArgs<IKinectElement> e);
        public event LostKinectFocusedEventHandler LostKinectFocused;
        public void OnLostKinectFocused(IKinectElement value)
        {
            if (LostKinectFocused != null)
                LostKinectFocused(this, new PayloadEventArgs<IKinectElement>(value));
        }
        #endregion

        private double ItemHeight { get; set; }

        public bool isScrolling { get; set; }
        public XnMPoint LastTouchPoint = new XnMPoint();
        public XnMPoint LastRealTouchPoint = new XnMPoint();
        public FrameworkElement ReferalUIElement;

        #region ScrollingZone
        /// <summary>
        /// The ScrollingZone Dependency
        /// </summary>
        public static readonly DependencyProperty ScrollingZoneProperty = DependencyProperty.RegisterAttached("ScrollingZone", typeof(double), typeof(KinectListBox), new FrameworkPropertyMetadata((double)0.2, new PropertyChangedCallback(OnScrollingZoneChanged)));
        /// <summary>
        /// The Header of your item
        /// </summary>
        public double ScrollingZone
        {
            get { return (double)GetValue(KinectListBox.ScrollingZoneProperty); }
            set
            {
                SetValue(KinectListBox.ScrollingZoneProperty, value);
            }
        }
        /// <summary>
        /// Handles changes to the ScrollingZone property.
        /// </summary>
        private static void OnScrollingZoneChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }
        #endregion

        #region AnimationDuration
        /// <summary>
        /// The AnimationDuration Dependency
        /// </summary>
        public static readonly DependencyProperty AnimationDurationProperty = DependencyProperty.RegisterAttached("AnimationDuration", typeof(int), typeof(KinectListBox), new FrameworkPropertyMetadata((int)200.0, new PropertyChangedCallback(OnAnimationDurationChanged)));
        /// <summary>
        /// The Header of your item
        /// </summary>
        public int AnimationDuration
        {
            get { return (int)GetValue(KinectListBox.AnimationDurationProperty); }
            set
            {
                SetValue(KinectListBox.AnimationDurationProperty, value);
            }
        }
        /// <summary>
        /// Handles changes to the AnimationDuration property.
        /// </summary>
        private static void OnAnimationDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }
        #endregion

        #region NumberOfItemsViewed
        /// <summary>
        /// The NumberOfItemsViewed Dependency
        /// </summary>
        public static readonly DependencyProperty NumberOfItemsViewedProperty = DependencyProperty.RegisterAttached("NumberOfItemsViewed", typeof(double), typeof(KinectListBox), new FrameworkPropertyMetadata((double)0.0, new PropertyChangedCallback(OnNumberOfItemsViewedChanged)));
        /// <summary>
        /// The Header of your item
        /// </summary>
        public double NumberOfItemsViewed
        {
            get { return (double)GetValue(KinectListBox.NumberOfItemsViewedProperty); }
            set
            {
                SetValue(KinectListBox.NumberOfItemsViewedProperty, value);
            }
        }
        /// <summary>
        /// Handles changes to the NumberOfItemsViewed property.
        /// </summary>
        private static void OnNumberOfItemsViewedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //((KinectListBox)d).UpdateItemsLayout();
        }
        #endregion

        #region ScrollOnWindow
        /// <summary>
        /// The ScrollOnWindow Dependency
        /// </summary>
        public static readonly DependencyProperty ScrollOnWindowProperty = DependencyProperty.RegisterAttached("ScrollOnWindow", typeof(bool), typeof(KinectListBox), new FrameworkPropertyMetadata((bool)false, new PropertyChangedCallback(OnScrollOnWindowChanged)));
        /// <summary>
        /// The Header of your item
        /// </summary>
        public bool ScrollOnWindow
        {
            get { return (bool)GetValue(KinectListBox.ScrollOnWindowProperty); }
            set
            {
                SetValue(KinectListBox.ScrollOnWindowProperty, value);
            }
        }
        /// <summary>
        /// Handles changes to the ScrollOnWindow property.
        /// </summary>
        private static void OnScrollOnWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }
        #endregion

        static KinectListBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(KinectListBox), new FrameworkPropertyMetadata(typeof(KinectListBox)));
        }

        public KinectListBox()
        {
            Unloaded += (s, e) =>
                {
                    if (!KinectManipulation.IsDesignTime && KinectWindow.GestureController(this)!=null)
                        KinectWindow.GestureController(this).ManipulationDetected -= new Controller.KinectManipulationController.ManipulationDetectedEventHandler(OnManipulationDetected);

                };
        }
        

        private void SmoothScrolling(KinectListBoxScrollViewer content, int to)
        {
            if (isScrolling)
                return;
            isScrolling = true;
            int from = Convert.ToInt32(content.VerticalOffset);
            var storyboard = new Storyboard();

            DoubleAnimation oScrollAnimation = new DoubleAnimation();
            oScrollAnimation.Completed += (se, ev) =>
            {
                isScrolling = false;
                SelectedIndex = contentListbox.SelectedIndex;
                Visibility = Visibility.Visible;
            };
            oScrollAnimation.From = from;
            oScrollAnimation.To = to;
            oScrollAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(AnimationDuration));
            content.BeginAnimation(KinectListBoxScrollViewer.VerticalOffsetAnimatedProperty, oScrollAnimation);
        }

        void ChangeSelection(int from, int to)
        {
            SmoothScrolling(contentAnimatedScrollViewer, Convert.ToInt32(Math.Floor(to * ItemHeight)));
        }

        public double CalculateItemsHeight(int index)
        {
            ListBoxItem lbi = contentListbox.ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem;
            if (lbi != null)
            {
                contentListbox.RefreshUI();
                ItemHeight = lbi.ActualHeight;
                return ItemHeight;
            }
            ItemHeight = 0;
            return ItemHeight;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            contentListbox = GetTemplateChild(ElementContentListbox) as ListBox;
            contentAnimatedScrollViewer = GetTemplateChild(ElementContentAnimatedScrollViewer) as KinectListBoxScrollViewer;
            contentSelectedZone = GetTemplateChild(ElementContentSelectedZone) as Rectangle;

            Visibility = Visibility.Hidden;
            DispatcherTimer dt = new DispatcherTimer();
            dt.Tick += (se, ev) =>
            {
                NumberOfItemsViewed = Convert.ToDouble(ActualHeight / CalculateItemsHeight(0));
                if (!double.IsInfinity(NumberOfItemsViewed))
                {
                    contentListbox.Margin = new Thickness(0, (((NumberOfItemsViewed - 1) / 2) * ItemHeight), 0, (((NumberOfItemsViewed - 1) / 2) * ItemHeight));
                }
            };
            dt.Interval = TimeSpan.FromMilliseconds(1);
            dt.Start();
            contentListbox.SelectionChanged += (se, ev) =>
            {
                ChangeSelection(SelectedIndex, contentListbox.SelectedIndex);
            };
            contentListbox.SelectedIndex = 0;
            ReferalUIElement = this;
            if (!KinectManipulation.IsDesignTime)
                KinectWindow.GestureController(this).ManipulationDetected += new Controller.KinectManipulationController.ManipulationDetectedEventHandler(OnManipulationDetected);
        }

        void OnManipulationDetected(object s, PayloadEventArgs<KinectManipulationDelta> e)
        {
            AttachScroll(new Point(e.Payload.Position.X, e.Payload.Position.Y), (FrameworkElement)this);
        }

        public void SetListSelectedIndex(int index)
        {
            contentListbox.SelectedIndex = index;
        }

        public void IncreaseListSelectedIndex()
        {
            if (!isScrolling)
            {
                contentListbox.SelectedIndex++;
            }
        }

        public void DescreaseListSelectedIndex()
        {
            if (!isScrolling)
            {
                if (contentListbox.SelectedIndex - 1 < 0)
                {
                    contentListbox.SelectedIndex = 0;
                    return;
                }
                contentListbox.SelectedIndex--;
            }
        }

        private void AttachScroll(Point point, FrameworkElement control)
        {
            if (KinectWindow.CurrentWindow(this) == null)
                return;
            if (ScrollOnWindow)
                control = KinectWindow.CurrentWindow(this);
            var pointTransform = control.TransformToVisual(KinectWindow.CurrentWindow(this));
            var hitPoint = new Point()
            {
                X = point.X - ((MatrixTransform)(pointTransform)).Value.OffsetX,
                Y = point.Y - ((MatrixTransform)(pointTransform)).Value.OffsetY
            };
            if (control.InputHitTest(hitPoint) != null)
            {
                var zoneTopTo = control.ActualHeight * ScrollingZone;
                var zoneBottomFrom = control.ActualHeight - control.ActualHeight * ScrollingZone;
                if (point.Y < zoneTopTo)
                    DescreaseListSelectedIndex();
                if (zoneBottomFrom < point.Y)
                    IncreaseListSelectedIndex();
            }

        }

        #region IKinectElement Members

        public void GotKinectFocus(ManagedNite.XnMPoint point, ManagedNite.XnMPoint realPoint, UIElement OriginalSource)
        {
            LastTouchPoint = point;
            LastRealTouchPoint = realPoint;
            OnGotKinectFocused(this);
        }

        public void LostKinectFocus()
        {
            LastTouchPoint = null;
            LastRealTouchPoint = null;
            OnLostKinectFocused(this);
        }

        #endregion
    }

    public class KinectListBoxScrollViewer : ScrollViewer
    {
        #region VerticalOffset
        /// <summary>
        /// VerticalOffset Attached Dependency Property

        /// </summary>
        public static readonly DependencyProperty VerticalOffsetAnimatedProperty = DependencyProperty.RegisterAttached("VerticalOffsetAnimated", typeof(double), typeof(KinectListBoxScrollViewer), new FrameworkPropertyMetadata((double)0.0, new PropertyChangedCallback(OnVerticalOffsetAnimatedChanged)));

        /// <summary>
        /// Gets the VerticalOffset property.  This dependency property 
        /// indicates ....
        /// </summary>
        public static double GetVerticalOffsetAnimated(DependencyObject d)
        {
            return (double)d.GetValue(VerticalOffsetAnimatedProperty);
        }
        /// <summary>
        /// Sets the VerticalOffset property.  This dependency property 
        /// indicates ....
        /// </summary>
        public static void SetVerticalOffsetAnimated(DependencyObject d, double value)
        {
            d.SetValue(VerticalOffsetAnimatedProperty, value);
        }/// <summary>
        /// Handles changes to the VerticalOffset property.
        /// </summary>
        private static void OnVerticalOffsetAnimatedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var viewer = (ScrollViewer)d;
            viewer.ScrollToVerticalOffset((double)e.NewValue);
        }
        #endregion

        #region HorizontalOffset
        /// <summary>
        /// HorizontalOffset Attached Dependency Property

        /// </summary>
        public static readonly DependencyProperty HorizontalOffsetAnimatedProperty = DependencyProperty.RegisterAttached("HorizontalOffsetAnimated", typeof(double), typeof(KinectListBoxScrollViewer), new FrameworkPropertyMetadata((double)0.0, new PropertyChangedCallback(OnHorizontalOffsetAnimatedChanged)));

        /// <summary>
        /// Gets the HorizontalOffset property.  This dependency property 
        /// indicates ....
        /// </summary>
        public static double GetHorizontalOffsetAnimated(DependencyObject d)
        {
            return (double)d.GetValue(HorizontalOffsetAnimatedProperty);
        }
        /// <summary>
        /// Sets the HorizontalOffset property.  This dependency property 
        /// indicates ....
        /// </summary>
        public static void SetHorizontalOffsetAnimated(DependencyObject d, double value)
        {
            d.SetValue(HorizontalOffsetAnimatedProperty, value);
        }/// <summary>
        /// Handles changes to the HorizontalOffset property.
        /// </summary>
        private static void OnHorizontalOffsetAnimatedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var viewer = (ScrollViewer)d;
            viewer.ScrollToHorizontalOffset((double)e.NewValue);
        }
        #endregion
    }
    public static class ListBoxExtensions
    {
        public static void RefreshUI(this DependencyObject obj)
        {
            obj.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Loaded, (Action)delegate { });
        }
    }
}
